package server;
import java.net.*;
import java.io.*;
import java.util.regex.*;
import common.*;

public class ClientThread implements Runnable 
{
	public SocketWrapper clientSocket = null;	
	ClientAccount account = null;
	
	// TODO: hack to allow more than one client on same PC, to be removed
	protected static int magic_number = 0;
	
	/*
	protected boolean deliverMessage(String from, String to, String message)
	{
		try
		{			
			int toIndex = ServerMain.findConnectedClient(to);						
			if(toIndex == -1) throw new Exception("Destination client not found online!");
			
			SocketWrapper socket = SocketWrapper.connect(ServerMain.connectedClients.get(toIndex).remoteAddress, Constants.CLIENT_PORT);
			socket.println("MESSAGE");
			socket.println(from);
			socket.println(String.valueOf(message.length()));
			socket.out.print(message);
			socket.close();
			
			return true;
		} // TRY
		catch(Exception e)
		{
			// All other exceptions are deadly
			System.err.println("In ClientThread::deliverMessage():");
			e.printStackTrace();
			return false;
		} // CATCH											
	}
	*/
	
	protected void broadCastStatusChange()
	{
		try
		{
			synchronized(ServerMain.connectedClients)
			{
				for(int i=0; i<ServerMain.connectedClients.size(); ++i)
					if(ServerMain.connectedClients.get(i) != this.account)						
					{			
						// Check that this account is not blocked
						int blockedAccountIndex = -1;
						for(int j=0; j<this.account.blockedContacts.size(); ++j)
							if(this.account.blockedContacts.get(j).equals(ServerMain.connectedClients.get(i).username))
							{
								blockedAccountIndex = j;
								break;
							}
						
							SocketWrapper socket = SocketWrapper.connect(ServerMain.connectedClients.get(i).remoteAddress, ServerMain.connectedClients.get(i).clientListeningPort); 
							socket.println("STATUS CHANGE");
							socket.println(this.account.username);
							
							if(blockedAccountIndex == -1)
								socket.println(String.valueOf(this.account.currentStatus));
							else
								socket.println(String.valueOf(Constants.ST_OFFLINE));
							
							String line = socket.readLine();
							if(line.indexOf("+OK") != 0)
								throw new Exception(line);						
							
							socket.close();
					} // IF
			} // SYNCHRONIZED
		} // TRY
		catch(Exception e)
		{
			// All other exceptions are deadly
			System.err.println("In ClientThread::broadCastStatusChange():");
			e.printStackTrace();
			return;
		} // CATCH											
	}
	
	protected void CloseConnection()
	{
		try
		{
			clientSocket.close();
			account.saveContact();
			
			synchronized(ServerMain.connectedClients)
			{
				if(!ServerMain.connectedClients.remove(account))
					System.err.println("Warning: account not found in global list when removing!");
			}
			
			System.out.println("Client "+account.username+" ("+account.remoteAddress+") disconnected");
		} // TRY
		catch(Exception e)
		{
			// All other exceptions are deadly
			System.err.println("In ClientThread::CloseConnection():");
			e.printStackTrace();
			System.exit(-1);
		} // CATCH			
		
	}
	
	protected void LoopForClientCommands()
	{
		String line = null;
		
		while(true)
		{
			try
			{
				line = clientSocket.readLine();
				
				if(line == null) continue;
				// ==================================================================== //
				if(line.equals("BLOCK"))
				{
					boolean alreadyPresent = false;
					String username = clientSocket.readLine();
					
					for(int i=0; i<this.account.blockedContacts.size(); ++i)
						if(this.account.blockedContacts.get(i).equals(username))
						{
							alreadyPresent = true;
							break;
						}
												
					if(!alreadyPresent)
					{
							this.account.blockedContacts.add(username);
							clientSocket.println("+OK client blocked");
							broadCastStatusChange();
					}
					else
						clientSocket.println("-ERR In Block: client "+username+" is already blocked");
					
				}
				
				else if(line.equals("UNBLOCK"))
				{					
					boolean removed = false;
					String username = clientSocket.readLine();
					
					for(int i=0; i<this.account.blockedContacts.size(); ++i)
						if(this.account.blockedContacts.get(i).equals(username))
						{
							this.account.blockedContacts.remove(i);
							removed = true;
							break;
						} // IF
					
					if(removed)
					{
						clientSocket.println("+OK client unblocked");
						broadCastStatusChange();
					} // IF
					else
						clientSocket.println("-ERR in Unblock: client "+username+" not found");
				}
				
				// ==================================================================== //
				else if(line.equals("QUIT"))
				{	
					clientSocket.println("+OK goodbye!");
					
					// Broadcast to everyone he's going offline
					account.currentStatus = Constants.ST_OFFLINE;
					broadCastStatusChange();
										
					CloseConnection();					
					return;
				}
				// ==================================================================== //
				else if(line.equals("REMOVE CONTACT"))
				{
					String username = clientSocket.readLine();
									
					for(int i=0; i<account.contacts.size(); ++i)
					{
						if(account.contacts.get(i).username.equals(username))
						{
							account.contacts.remove(i);
							break;
						} // IF
					} // FOR

					clientSocket.println("+OK");
				}
				
				else if(line.equals("ADD CONTACT"))
				{
					String username = clientSocket.readLine();					
					boolean contactAlreadyAdded = false;
					
					if(ClientAccount.ClientExists(username))						
					{
						// Don't add the same contact twice
						for(int i=0; i<account.contacts.size(); ++i)
						{
							if(account.contacts.get(i).username.equals(username))
							{
								contactAlreadyAdded = true;
								clientSocket.println("-ERR username already on your contact list");
								break;
							} // IF
						}
						
						if(!contactAlreadyAdded)												
						{
							System.out.println("step 0");
							ContactListItem newContact = new ContactListItem();
							System.out.println("step 1");
							newContact.username = username;
							System.out.println("step 2");
							newContact.group = "";
							System.out.println("step 3");
									
							account.contacts.add(newContact);
							System.out.println("step 4");
							clientSocket.println("+OK contact added");
							System.out.println("step 5");
							int newContactIndex = ServerMain.findConnectedClient(username);
							System.out.println("step 6");
									
							if(newContactIndex == -1)
							{
								System.out.println("Sending new contact status as offline");
								clientSocket.println(String.valueOf(Constants.ST_OFFLINE));
							} // IF
							else
							{
								synchronized(ServerMain.connectedClients)
								{
									System.out.println("Sending new contact status as "+ServerMain.connectedClients.get(newContactIndex).currentStatus);
									clientSocket.println(String.valueOf(ServerMain.connectedClients.get(newContactIndex).currentStatus));
								}
							} // ELSE
						} // IF
					} // IF
					else
						clientSocket.println("-ERR Account not found");
				} // IF
				// ==================================================================== //
				else if(line.equals("GET IP"))
				{
					String username = clientSocket.readLine();
					boolean clientFound = false;
					
					System.out.println("Checking online status of user "+username);
					
					synchronized(ServerMain.connectedClients)
					{
						for(int i=0; i<ServerMain.connectedClients.size(); ++i)
						{
							if(ServerMain.connectedClients.get(i).username.equals(username))
							{
								clientSocket.println("+OK client IP follows");
								clientSocket.println(ServerMain.connectedClients.get(i).remoteAddress);
								clientSocket.println(String.valueOf(ServerMain.connectedClients.get(i).clientListeningPort));
								clientFound = true;
							} // IF
						} // FOR
					} // SYNCHRONIZED
					
					if(!clientFound)
						clientSocket.println("-ERR client not online");					
				} // IF
				// ==================================================================== //
				else if(line.equals("CHANGE STATUS"))
				{					
					account.currentStatus = Integer.valueOf(clientSocket.readLine()).intValue();
					clientSocket.println("+OK Status Changed");
					broadCastStatusChange();
				} // IF
				 // ==================================================================== //
				/*
				if(line.equals("MESSAGE"))
				{
					String destination = clientSocket.readLine();
					int msgSize = Integer.valueOf(clientSocket.readLine()).intValue();
					String message = "";
					
					for(int i=0; i<msgSize; ++i)
						message += (char) clientSocket.in.read();										
					
					if(deliverMessage(account.username, destination, message))
						clientSocket.println("+OK message delivered");
					else
						clientSocket.println("-ERR error during delivery");										
				}
				*/
				
				else if(line.equals("REGEXP SEARCH"))
				{
					String regexp = clientSocket.readLine().toLowerCase().trim();
					File dir = new File(Utils.ContactsFolder);
					String files[] = dir.list();
					//java.util.regex.Pattern p = Pattern.compile(regexp);
										
					for(int i=0; i<files.length; ++i)
						//if(p.matcher(files[i]).matches())
						//if(Pattern.matches(regexp, files[i]))
						if(files[i].toLowerCase().indexOf(regexp) != -1)
							clientSocket.println(files[i]);
					
					clientSocket.println("+OK all results sent");
				}
				
				else if(line.equals("ASSIGN TO GROUP"))
				{
					String username = clientSocket.readLine();
					String newGroup = clientSocket.readLine();																				
					int accountIndex = this.account.searchContactList(username);
					
					if(accountIndex == -1)
						clientSocket.println("-ERR contact name not found");
					else
					{
						this.account.contacts.get(accountIndex).group = newGroup;
						clientSocket.println("+OK new group saved");
					} // ELSE
				} // ELSE
				
				else
				{
					clientSocket.println("-ERR unrecognized command");
				}
				
			} // TRY
			catch(SocketTimeoutException e)
			{
				// do nothing, we can loop again and listen for further connections
			}
			catch(Exception e)
			{
				// All other exceptions are deadly
				System.err.println("In ClientThread::LoopForClientCommands():");
				e.printStackTrace();
				CloseConnection();
				System.exit(-1);
			} // CATCH			
			
		} // WHILE
	}
	
	public void run() 
	{
		String username = "", password = "";		
		boolean isANewAccount;
		
		try
		{			
			// ask for username and password
			clientSocket.println("+OK Welcome to MSN-clone, username & pwd pleaz");
			username = clientSocket.readLine();			
			password = clientSocket.readLine();
			
			// Authentication stage
			isANewAccount = !ClientAccount.ClientExists(username);
			account = ClientAccount.LoadContact(username);
			account.username = username;
			if(isANewAccount) account.password = password;
			
			// TODO: hack for multiple clients (see above), to be removed
			account.clientListeningPort = Constants.CLIENT_PORT+magic_number;
			++magic_number;			
			clientSocket.println(String.valueOf(account.clientListeningPort));

			// Prevent duplicate connections
			synchronized(ServerMain.connectedClients)
			{
				for(int i=0; i<ServerMain.connectedClients.size(); ++i)
					if(ServerMain.connectedClients.get(i).username.equals(username))
					{
						clientSocket.println("-ERR this username is already connected");
						clientSocket.close();
						return;
					} // IF
			}
			
			if(account.password.equals(password))
			{												
				// send contacts list
				clientSocket.println("+OK contacts & their statuses follow");
				
				for(int i=0; i<account.contacts.size(); ++i)
				{
					clientSocket.println(account.contacts.get(i).username);
					clientSocket.println(account.contacts.get(i).group);
					
					// get this contact's status
					int status = Constants.ST_OFFLINE;
					
					int j = ServerMain.findConnectedClient(account.contacts.get(i).username);
					
					if(j != -1)
					{						
						synchronized(ServerMain.connectedClients)
						{
							// Check that that account isn't blocking this one
							int blockedAccountIndex = -1;
							for(int k=0; k<ServerMain.connectedClients.get(j).blockedContacts.size(); ++k)
								if(ServerMain.connectedClients.get(j).blockedContacts.get(k).equals(this.account.username))
								{
									blockedAccountIndex = j;
									break;
								}
							
							if(blockedAccountIndex == -1)
								status = ServerMain.connectedClients.get(j).currentStatus;
						}
					} // IF
					clientSocket.println(String.valueOf(status));
					
					// Determine if this account is blocked
					int blockedAccountIndex = -1;
					for(j=0; j<account.blockedContacts.size(); ++j)
						if(account.blockedContacts.get(j).equals(account.contacts.get(i).username))
						{
							blockedAccountIndex = j;
							break;
						}
					
					if(blockedAccountIndex == -1)
						clientSocket.println("0");
					else
						clientSocket.println("1");
					//clientSocket.println(String.valueOf(account.contacts.get(i).))
				} // FOR
								
				clientSocket.println("+OK End of contacts");
				
				// add it to the list of connected clients
				synchronized(ServerMain.connectedClients)
				{ 
					ServerMain.connectedClients.add(account); 
				}				
				
				// start listening to commands...
				account.remoteAddress = clientSocket.socket.getInetAddress().toString();
				while(account.remoteAddress.indexOf("/") == 0)
					account.remoteAddress = account.remoteAddress.substring(1);
				
				broadCastStatusChange();
				LoopForClientCommands();
			} // IF
			else
			{
				clientSocket.println("-ERR password problem");
				clientSocket.close();
				return;
			} // ELSE
		} // TRY
		catch(Exception e)
		{
			System.err.println("In ClientThread::run():");
			e.printStackTrace();
			System.exit(-1);			
		}
	}
}